using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using Emgu.CV;
using Emgu.Util;
using System.Threading;

namespace MotionDetection
{
   public partial class Form1 : Form
   {
    public class Holding
    {
        public Holding(int x, int y, int width, int height)
        {
            m_rect = new MCvRect(x, y, width, height);
            m_count = 0;
            m_mark = false;
        }

        public MCvRect m_rect;
        public int m_count = 0;
        public bool m_mark = false;
    };

      private Capture _capture;
      private Thread _captureThread;
      private MotionHistory _motionHistory;

       private List<Holding> holdList;
       private Holding maxHold;

      public Form1()
      {
         InitializeComponent();

         //try to create the capture
         if (_capture == null)
         {
            try
            {
                _capture = new Capture("d:/a.avi");
                //_capture = new Capture();
            }
            catch (NullReferenceException excpt)
            {   //show errors if there is any
               MessageBox.Show(excpt.Message);
            }
         }

         holdList = new List<Holding>();
         //holdList.Add(new Holding(200, 100, 30, 30));
         //holdList.Add(new Holding(100, 200, 30, 30));
         //holdList.Add(new Holding(250, 320, 30, 30));

         holdList.Add(new Holding(198, 287, 30, 30));
         holdList.Add(new Holding(247, 312, 30, 30));
         holdList.Add(new Holding(280, 285, 30, 30));
         holdList.Add(new Holding(291, 251, 30, 30));
         holdList.Add(new Holding(321, 254, 30, 30));
         holdList.Add(new Holding(351, 274, 30, 30));
         holdList.Add(new Holding(366, 311, 30, 30));
         holdList.Add(new Holding(397, 241, 30, 30));
         holdList.Add(new Holding(427, 260, 30, 30));
         holdList.Add(new Holding(460, 280, 30, 30));
         holdList.Add(new Holding(484, 242, 30, 30));
         holdList.Add(new Holding(486, 286, 30, 30));
         holdList.Add(new Holding(512, 264, 30, 30));
         holdList.Add(new Holding(542, 243, 30, 30));
         holdList.Add(new Holding(572, 266, 30, 30));
         holdList.Add(new Holding(626, 264, 30, 30));
         holdList.Add(new Holding(653, 242, 30, 30));
         //holdList.Add(new Holding(683, 286, 30, 30));
         holdList.Add(new Holding(694, 251, 30, 30));

         maxHold = holdList[0];

         if (_capture != null) //if camera capture has been successfully created
         {   
            _motionHistory = new MotionHistory(
                6, //number of images to store in buffer, adjust it to fit your camera's frame rate
                20, //0-255, the amount of pixel intensity change to consider it as motion pixel
                1.0, //in second, the duration of motion history you wants to keep
                0.05, //in second, parameter for cvCalcMotionGradient
                0.5); //in second, parameter for cvCalcMotionGradient

            _captureThread = new Thread(ProcessImage);
            _captureThread.Start();
         }
      }

      public void ProcessImage()
      {
         while (true)
         {
            Image<Bgr, Byte> image = _capture.QuerySmallFrame().PyrUp(); //reduce noise from the image

            //update the motion history
            _motionHistory.Update(image.Convert<Gray, Byte>());

            /*#region get a copy of the motion mask and enhance its color
            Image<Gray, Byte> motionMask = _motionHistory.Mask;
            double[] minValues, maxValues; 
            MCvPoint[] minLoc, maxLoc;
            motionMask.MinMax(out minValues, out maxValues, out minLoc, out maxLoc);
            motionMask = motionMask * (255.0 / maxValues[0]);
            #endregion

            //create the motion image 
            Image<Bgr, Byte> motionImage = new Image<Bgr, byte>(motionMask.Width, motionMask.Height);
            //display the motion pixels in blue (first channel)
            motionImage[0] = motionMask;
            */

            //Threshold to define a motion area, reduce the value to detect smaller motion
            double minArea = 100;
             
            foreach (Holding hold in holdList)
            {
                image.Draw(new Rectangle<int>(hold.m_rect), new Bgr(hold.m_mark ? Color.Red : Color.Blue), 2);

                continue;

                if (hold.m_mark) continue;

                double angle, motionPixelCount;
                _motionHistory.MotionInfo(hold.m_rect, out angle, out motionPixelCount);
                if (motionPixelCount > 50)
                {
                    if ((angle > 260 && angle < 280) || (angle > 80 && angle < 100))
                    {
                        // rope
                        hold.m_count = 0;
                    }
                    else
                    {
                        hold.m_count++;
                        if (hold.m_count > 3)
                        {
                            //hold.m_mark = true;
                        }
                    }
                }
                if (motionPixelCount < 10)
                {
                    hold.m_count = 0;
                }
            }

            Seq<MCvConnectedComp> motionComponents = _motionHistory.MotionComponents;

            //iterate through each of the motion component
            foreach (MCvConnectedComp comp in motionComponents)
            {
                if (comp.rect.y < 200) continue;

                // Drop the rope rectangles
                if (comp.rect.width / comp.rect.height > 4)
                {
                    //image.Draw(new Rectangle<int>(comp.rect), new Bgr(Color.Green), 2);
                    continue;
                }

               //reject the components that have small area;
               if (comp.rect.Area < minArea) continue;

               // find the angle and motion pixel count of the specific area
               double angle, motionPixelCount;
               //_motionHistory.MotionInfo(comp.rect, out angle, out motionPixelCount);
               _motionHistory.MotionInfo(comp.rect, out angle, out motionPixelCount);

               //reject the area that contains too few motion
               if (motionPixelCount / comp.area < 0.05) continue;

               //Draw each individual motion in red
               //image.Draw(new Rectangle<int>(comp.rect), new Bgr(Color.Yellow), 2);

               foreach (Holding hold in holdList)
               {
                   if ((!hold.m_mark) &&
                       (hold.m_rect.x >= maxHold.m_rect.x) &&
                       (comp.rect.x > hold.m_rect.x) &&
                       (comp.rect.x + comp.rect.width < hold.m_rect.x + hold.m_rect.width) &&
                       (comp.rect.y > hold.m_rect.y) &&
                       (comp.rect.y + comp.rect.height < hold.m_rect.y + hold.m_rect.height))
                   {
                       hold.m_mark = true;
                       maxHold = hold;
                       break;
                   }
               }
               //capturedImageBox.Image = image;
               //DrawMotion(motionImage, comp.rect, angle, new Bgr(Color.Red));
            }

            //Image<Bgr, byte> rotimage = image.Rotate(270, new Bgr(Color.Black));
             capturedImageBox.Image = image.Rotate(270, new Bgr(Color.Black), false);
            //capturedImageBox.Image = rotimage;
             //capturedImageBox.
            //capturedImageBox.Size = new Size(image.Height, image.Width);

/*            // find and draw the overall motion angle
            double overallAngle, overallMotionPixelCount;
            _motionHistory.MotionInfo(motionMask.ROI.MCvRect, out overallAngle, out overallMotionPixelCount);
            DrawMotion(motionImage, motionMask.ROI.MCvRect, overallAngle, new Bgr(Color.Green));

            //Display the amount of motions found on the current image
            UpdateText(String.Format("Total Motions found: {0}; Motion Pixel count: {1}", motionComponents.Total, overallMotionPixelCount));

            //Display the image of the motion
            motionImageBox.Image = motionImage;
*/
            //The following is optional, it force a garbage collection and free unused memory
            //If removed the program consumes more memory but runs slightly faster
            GC.Collect();
         }
      }

      private void UpdateText(String text)
      {
         if (InvokeRequired)
         {
            this.Invoke(new Action<String>(UpdateText), new object[] { text });
         }
         else
         {
            label3.Text = text;
         }
      }

      private static void DrawMotion(Image<Bgr, Byte> image, MCvRect motionArea, double angle, Bgr color)
      {
         int circleRadius = (int)(motionArea.width + motionArea.height) / 4;

         Rectangle<int> rect = new Rectangle<int>(motionArea);
         Circle<int> circle = new Circle<int>(rect.Center, circleRadius);

         int xDirection = (int)(Math.Cos(angle * Math.PI / 180.0) * circleRadius);
         int yDirection = (int)(Math.Sin(angle * Math.PI / 180.0) * circleRadius);
         Point2D<int> pointOnCircle = new Point2D<int>(
             circle.Center.X + xDirection,
             circle.Center.Y + yDirection);
         LineSegment2D<int> line = new LineSegment2D<int>(circle.Center, pointOnCircle);

         image.Draw(circle, color, 1);
         image.Draw(line, color, 2);
      }

      /// <summary>
      /// Clean up any resources being used.
      /// </summary>
      /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
      protected override void Dispose(bool disposing)
      {
         if (disposing && (components != null))
         {
            components.Dispose();
         }

         if (_captureThread != null) _captureThread.Abort();

         base.Dispose(disposing);
      }
       

       private void capturedImageBox_MouseClick(object sender, MouseEventArgs e)
       {
           label1.Text = "x: " + e.X.ToString() + " y: " + e.Y.ToString();
           holdList.Add(new Holding(720 - e.Y - 15, e.X - 15, 30, 30));
       }
   }
}